package com.intertid.mongodb.commons
import net.sf.json.util.PropertyFilter
import net.sf.json.JsonConfig
import net.sf.json.JSONObject
import com.intertid.mongodb.annotations.Ref
import net.sf.json.processors.JsonValueProcessorMatcher
import com.intertid.mongodb.Mapper
import com.intertid.mongodb.annotations.ID
import net.sf.json.processors.PropertyNameProcessor
import org.bson.types.ObjectId
import com.mongodb.DBObject
import com.mongodb.util.JSON
import java.util.Set
import net.sf.json.processors.JsonBeanProcessor
import java.beans.PropertyDescriptor
import org.apache.commons.beanutils.PropertyUtils
import com.intertid.mongodb.annotations.Transient
import com.intertid.mongodb.RefBean
import java.lang.reflect.Proxy
import com.intertid.mongodb.LazyLoadDynamicProxy
import com.mongodb.BasicDBObject
import com.mongodb.casbah.commons.MongoDBObject
import sun.reflect.misc.FieldUtil
import com.intertid.mongodb.error.MongoDBException
import com.intertid.mongodb.error.MongoDBErrorDefine
import java.util.List
import java.util.ArrayList
import com.intertid.scala.commons._
import com.mongodb.DBRef
import java.util.Collection
import com.mongodb.BasicDBList
import java.util.HashSet
import org.slf4j.LoggerFactory
import java.io.Serializable
import com.intertid.mongodb.ProxyTag
import org.apache.commons.lang.ClassUtils

object MongoDBUtils {

  final val log = LoggerFactory.getLogger(MongoDBUtils.getClass());

  final val MONGODB_PK_NAME = "_id";

  private def createRefProxy(dbobj: DBObject, clsLoader: ClassLoader, supers: Array[Class[_]]): Object = {
    val refBean = new RefBean();
    refBean.`set$id`(dbobj.get("$id"));
    refBean.className = dbobj.get("className").toString();
    //val proxy = Proxy.newProxyInstance(clsLoader, supers, new LazyLoadDynamicProxy(refBean));
    val proxy = new LazyLoadDynamicProxy(refBean).createProxy();
    proxy
  }

  @deprecated
  private def createRefProxy(dbref: DBRef, clsLoader: ClassLoader, supers: Array[Class[_]]): Object = { //有问题
    val refBean = new RefBean()
    val collectionName = dbref.getRef();

    val cls = Mapper.mappings.findItemKey((k, v) => {
      v.toString().equals(collectionName);
    })

    refBean.className = cls.get.getName();
    refBean.dbref = dbref;
    refBean.`set$id`(dbref.getId());

    //val proxy = Proxy.newProxyInstance(clsLoader, Array[Class[_]](classOf[Serializable]), new LazyLoadDynamicProxy(refBean));
    val proxy = new LazyLoadDynamicProxy(refBean).createProxy();
    proxy;
  }

  private def createRefProxy(dbref: DBRef, propertyType: Class[_], clsLoader: ClassLoader, supers: Array[Class[_]]): Object = {
    val refBean = new RefBean()
    val collectionName = dbref.getRef();

    refBean.className = propertyType.getName();
    refBean.dbref = dbref;
    refBean.`set$id`(dbref.getId());

    println("$$" + refBean.className);
    //val proxy = Proxy.newProxyInstance(clsLoader, Array[Class[_]](classOf[Serializable]), new LazyLoadDynamicProxy(refBean));
    val proxy = new LazyLoadDynamicProxy(refBean).createProxy();
    proxy;
  }

  def dbObject2Bean(data: DBObject): Object = dbObject2Bean(data, Class.forName(data.get("className").toString()));

  def dbObject2Bean(data: DBObject, classz: Class[_]): Object = {

    if (data == null)
      return null;

    val bean = classz.newInstance();

    val properties = PropertyUtils.getPropertyDescriptors(classz).filter(pd => {

      var result = true;
      try {
        result = !classz.getDeclaredField2(
          pd.getName()).isAnnotationPresent(classOf[Transient]);
      } catch {

        case e: java.lang.NoSuchFieldException => {

          result = false;
        }
      }
      result;
    });

    properties.foreach(pd => {

      val f = classz.getDeclaredField2(pd.getName());
      if (f.isAnnotationPresent(classOf[ID])) {

        val id = data.get(MONGODB_PK_NAME);
        if (pd.getPropertyType().equals(classOf[ObjectId])) {
          pd.getWriteMethod().invoke(bean, id);
        } else if (pd.getPropertyType().equals(classOf[String])) {
          pd.getWriteMethod().invoke(bean, id.toString());
        }
      } else if (f.isAnnotationPresent(classOf[Ref])) {
        val value = data.get(pd.getName());
        val ref = f.getAnnotation(classOf[Ref]);

        val concreteClass = if (ref.concreteClass().equals(classOf[Object])) pd.getPropertyType() else ref.concreteClass();
        if (value != null) {
          if (classOf[java.util.Collection[_]].isAssignableFrom(pd.getPropertyType())) {
            val dblist = value.asInstanceOf[BasicDBList];
            val iter = dblist.iterator();
            val list = if (pd.getPropertyType().isAssignableFrom(classOf[java.util.List[_]])) new ArrayList[Object](dblist.size); else new HashSet[Object](dblist.size);
            while (iter.hasNext()) {
              val dbobj = iter.next();

              val proxy = if (dbobj.isInstanceOf[DBRef]) {

                createRefProxy(dbobj.asInstanceOf[DBRef], concreteClass, pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
              } else {

                createRefProxy(dbobj.asInstanceOf[DBObject], pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
              }
              println("----->" + proxy);
              list.add(proxy);
            }
            pd.getWriteMethod().invoke(bean, list);
          } else if (classOf[scala.List[_]].isAssignableFrom(pd.getPropertyType())) {
            val dblist = value.asInstanceOf[BasicDBList];
            val iter = dblist.iterator();
            var list: scala.List[Object] = Nil;
            while (iter.hasNext()) {
              val dbobj = iter.next();
              val proxy = if (dbobj.isInstanceOf[DBRef]) {
                createRefProxy(dbobj.asInstanceOf[DBRef], concreteClass, pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
              } else {
                createRefProxy(dbobj.asInstanceOf[DBObject], pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
              }
              list = list ::: scala.List(proxy)
            }
            pd.getWriteMethod().invoke(bean, list);
          } else if (pd.getPropertyType().isArray()) {
            val dblist = value.asInstanceOf[BasicDBList];
            val iter = dblist.iterator();
            var array: Array[Object] = new Array(dblist.size());
            var i = 0;
            while (iter.hasNext()) {
              val dbobj = iter.next();
              val proxy = if (dbobj.isInstanceOf[DBRef]) {
                createRefProxy(dbobj.asInstanceOf[DBRef], concreteClass, pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
              } else {
                createRefProxy(dbobj.asInstanceOf[DBObject], pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
              }
              array(i) = proxy
              i = i + 1;
            }
            pd.getWriteMethod().invoke(bean, array);
          } else {
            logout(pd.getName() + ":" + pd.getPropertyType());
            val refData = data.get(pd.getName()).asInstanceOf[DBRef];
            val proxy = createRefProxy(refData, concreteClass, pd.getPropertyType().getClassLoader(), Array[Class[_]](pd.getPropertyType()));
            pd.getWriteMethod().invoke(bean, proxy);
          }
        }
      } else {
        val value = data.get(pd.getName());
        if (Mapper.exists(pd.getPropertyType())) {

          val dbobj = value.asInstanceOf[DBObject];
          pd.getWriteMethod().invoke(bean, dbObject2Bean(dbobj, pd.getPropertyType()));
        } else {

          pd.getWriteMethod().invoke(bean, value);
        }
      }
    });
    return bean.asInstanceOf[Object];
  }

  def getRealClass(pc: Class[_]): Class[_] = {

    return if (!pc.getInterfaces().contains(classOf[ProxyTag])) {
      pc;
    } else {
      val index = pc.getName().indexOf("$");
      if (index == (-1)) {
        pc;
      } else {
        Class.forName(pc.getName().substring(0, index));
      }
    }
  }

  private def createRefBean(bean: Object): RefBean = createRefBean(bean, null);

  private def createRefBean(bean: Object, sourceClass: Class[_]): RefBean = {
    val refBean = new RefBean;
    val cls = if (sourceClass == null) getRealClass(bean.getClass()) else sourceClass;

    refBean.className = cls.getName();
    val idFieldItem = cls.getDeclaredFields().find(f => f.isAnnotationPresent(classOf[ID]));
    if (!idFieldItem.isEmpty) {
      val propDesc = PropertyUtils.getPropertyDescriptor(bean, idFieldItem.get.getName());
      println("!@#!@#!" + (bean == null) + "::" + cls + ":" + propDesc.getName() + ":" + bean.getClass().getSuperclass() + ":" + propDesc.getReadMethod().getName() + ":" + propDesc.getReadMethod().getTypeParameters().length);
      val id = propDesc.getReadMethod().invoke(bean);
      if (id == null)
        throw new MongoDBException(MongoDBErrorDefine.REF_ID_NULL);
      refBean.set$id(id);
    }
    return refBean;
  }

  def bean2DBObject(bean: Object, children: Boolean = false): DBObject = {

    if (bean == null)
      return null;

    val builder = MongoDBObject.newBuilder;
    val beanCls = bean.getClass();
    val props = PropertyUtils.getPropertyDescriptors(beanCls);
    props.foreach(p => {

      try {
        val f = beanCls.getDeclaredField2(p.getName());

        if (f != null) {
          if (!f.isAnnotationPresent(classOf[Transient])) {
            if (f.isAnnotationPresent(classOf[ID])) {
              if (!children) {
                val id = p.getReadMethod().invoke(bean);
                builder += "_id" -> (if (id.isInstanceOf[String]) new ObjectId(id.toString()) else id);
              }
            } else {

              val childValue = p.getReadMethod().invoke(bean);
              if (childValue != null) {

                if (f.isAnnotationPresent(classOf[Ref])) {
                  if (childValue.isInstanceOf[java.util.Collection[_]]) {

                    if (childValue.asInstanceOf[java.util.Collection[_]].size() > 0) {
                      val refList = new ArrayList[DBRef](childValue.asInstanceOf[java.util.Collection[_]].size());
                      childValue.asInstanceOf[java.util.Collection[_]].foreach(v => {

                        val refbean = createRefBean(v.asInstanceOf[Object]);
                        refList.add(refbean.toDBObject);
                      })
                      builder += p.getName() -> refList;
                    }
                  } else if (childValue.isInstanceOf[scala.List[_]]) {
                    if (childValue.asInstanceOf[scala.List[_]].length > 0) {
                      val refList = new ArrayList[DBRef](childValue.asInstanceOf[scala.List[_]].length);
                      childValue.asInstanceOf[scala.List[_]].foreach(v => {
                        val refbean = createRefBean(v.asInstanceOf[Object]);
                        refList.add(refbean.toDBObject);
                      })
                      builder += p.getName() -> refList;
                    }
                  } else if (childValue.getClass().isArray()) {
                    if (childValue.asInstanceOf[Array[_]].length > 0) {
                      val refList = new ArrayList[DBRef](childValue.asInstanceOf[Array[_]].length);
                      childValue.asInstanceOf[Array[_]].foreach(v => {
                        val refbean = createRefBean(v.asInstanceOf[Object]);
                        refList.add(refbean.toDBObject);
                      })
                      builder += p.getName() -> refList;
                    }
                  } else {
                    builder += p.getName() -> createRefBean(childValue, p.getPropertyType()).toDBObject();
                  }
                } else {
                  val existMapper = Mapper.exists(p.getPropertyType());
                  if (!existMapper) {
                    builder += p.getName() -> p.getReadMethod().invoke(bean);
                  } else {

                    val childBean = bean2DBObject(childValue, true);

                    builder += p.getName() -> childBean;
                  }
                }
              }

            }
          }
        }
      } catch {
        case _: NoSuchFieldException => {}
      }
    });

    if (!children)
      builder += "className" -> bean.getClass().getName();
    builder.result();

  }

  def findIDProperty(cls: Class[_]): PropertyDescriptor = {
    val name = findIDPropertyName(cls);
    if (name == null)
      null
    else {
      val found = PropertyUtils.getPropertyDescriptors(cls).find(pd => { pd.getName().equals(name) });
      if (!found.isEmpty)
        found.get
      else
        null
    }

  }

  def findIDPropertyName(cls: Class[_]): String = {

    var idField = cls.getDeclaredFields().find(f => { f.isAnnotationPresent(classOf[ID]) });

    if (idField.isEmpty) {
      val superclasses = ClassUtils.getAllSuperclasses(cls);
      superclasses.foreach(c => {
        idField = c.asInstanceOf[Class[_]].getDeclaredFields().find(f => { f.isAnnotationPresent(classOf[ID]) });
        if (!idField.isEmpty)
          return idField.get.getName;
      })
    } else {
      return idField.get.getName();
    }

    return null;
  }
}